﻿//==============================================================================
// XA2AudioPlayer 
//==============================================================================

#include "stdafx.h"
#include "../../AudioUtil.h"
#include "../../Resource/AudioSource.h"
#include "XA2AudioPlayer.h"

namespace LNote
{
namespace Core
{
namespace Audio
{
namespace XAudio2
{

//==============================================================================
// XAudio2AudioPlayerBase 
//==============================================================================

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    XAudio2AudioPlayerBase::XAudio2AudioPlayerBase( AudioDevice* manager )
        : Audio::AudioPlayerBase    ( manager )
        , mManager                  ( manager )
		, mEmitterState             ( NULL )
    {
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    XAudio2AudioPlayerBase::~XAudio2AudioPlayerBase()
    {
        SAFE_DELETE( mEmitterState );
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    void XAudio2AudioPlayerBase::initialize( AudioSourceBase* audio_source, bool enable_3d )
    {
		AudioPlayerBase::initialize( audio_source, enable_3d );

		if ( enable_3d )
		{
            mEmitterState = LN_NEW EmitterState( this->mAudioSource->getWaveFormat()->Channels );
		}
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    void XAudio2AudioPlayerBase::setPosition( const LVector3& pos )
    {
        if ( mEmitterState )
        {
            mEmitterState->Position = pos;
        }
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    void XAudio2AudioPlayerBase::setVelocity( const LVector3& v )
    {
        if ( mEmitterState )
        {
            mEmitterState->Velocity = v;
        }
    }


//==============================================================================
// OnMemory
//==============================================================================

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	OnMemory::OnMemory( AudioDevice* manager )
		: XAudio2AudioPlayerBase    ( manager )
        , mSourceVoice				( NULL )
	{
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	OnMemory::~OnMemory()
	{
		if ( mSourceVoice )
		{
			mSourceVoice->Stop();
			mSourceVoice->FlushSourceBuffers();
			mSourceVoice->DestroyVoice();
			mSourceVoice = NULL;
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    void OnMemory::initialize( AudioSourceBase* audio_source, bool enable_3d )
    {
		LN_ERR2_ASSERT( audio_source );

		XAudio2AudioPlayerBase::initialize( audio_source, enable_3d );

		// オンメモリ再生用に内部に持ってるバッファを埋める
		mAudioSource->fillBufferSafe();
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void OnMemory::setVolume( int volume )
	{
        XAudio2AudioPlayerBase::setVolume( volume );
        if ( mSourceVoice )
		{
			HRESULT hr = mSourceVoice->SetVolume( this->mVolume * 0.01f );
			LN_THROW_Com( hr );
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void OnMemory::setPitch( int pitch )
	{
        XAudio2AudioPlayerBase::setPitch( pitch );
        if ( mSourceVoice )
		{
			HRESULT hr = mSourceVoice->SetFrequencyRatio( this->mPitch * 0.01f );
			LN_THROW_Com( hr );
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    lnU64 OnMemory::getPlayedSamples() const
    {
        if ( mSourceVoice )
        {
            XAUDIO2_VOICE_STATE voice_state;
		    mSourceVoice->GetState( &voice_state );
		    return voice_state.SamplesPlayed;
        }
        return 0;
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void OnMemory::play()
	{
        stop();

        // XAudio のソースボイス作成
        HRESULT hr;
        const WaveFormat* ln_format = this->mAudioSource->getWaveFormat();
		WAVEFORMATEX format;
        AudioUtil::convLNWaveFormatToWAVEFORMATEX( *ln_format, &format );

        hr = this->mManager->getXAudio2()->CreateSourceVoice( &mSourceVoice, &format );
		LN_THROW_Com( hr );

		hr = mSourceVoice->FlushSourceBuffers();
		LN_THROW_Com( hr );
        
        // SourceVoiceに送信するデータ
        XAUDIO2_BUFFER submit = { 0 };
		submit.AudioBytes = this->mAudioSource->getOnmemoryPCMBufferSize();		// バッファのバイト数
		submit.pAudioData = (lnU8*)this->mAudioSource->getOnmemoryPCMBuffer();	// バッファの先頭アドレス
		submit.Flags = XAUDIO2_END_OF_STREAM;

		// ループ再生する場合
		if ( mIsLoop )
		{
			submit.LoopCount	= XAUDIO2_LOOP_INFINITE;	// 無限ループ
			submit.LoopBegin	= this->mLoopBegin;
			submit.LoopLength	= this->mLoopLength;
		}
		// ループ再生しない場合
		else
		{
			submit.LoopBegin = XAUDIO2_NO_LOOP_REGION;
		}

		// ボイスキューにデータを送る
		hr = mSourceVoice->SubmitSourceBuffer( &submit );
		LN_THROW_Com( hr );

        hr = mSourceVoice->SetVolume( this->mVolume * 0.01f );
		LN_THROW_Com( hr );

        hr = mSourceVoice->SetFrequencyRatio( this->mPitch * 0.01f );
		LN_THROW_Com( hr );

		// 再生開始
		hr = mSourceVoice->Start();
		LN_THROW_Com( hr );

		this->mIsPlaying = true;
		this->mIsPausing = false;

		polling();	// 3D オーディオの計算を行うため
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void OnMemory::stop()
	{
        if ( mSourceVoice )
		{
			HRESULT hr;

			hr = mSourceVoice->Stop();
			LN_THROW_Com( hr );

			hr = mSourceVoice->FlushSourceBuffers();
			LN_THROW_Com( hr );

			mSourceVoice->DestroyVoice();
			mSourceVoice = NULL;
		}

		this->mIsPlaying = false;
		this->mIsPausing = false;
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void OnMemory::pause( bool isPause )
	{
		HRESULT hr;

		// 再生中の場合
		if ( mSourceVoice && mIsPlaying )
		{
			// 一時停止する場合で、現在一時停止していない場合
			if ( isPause && !mIsPausing )
			{
				hr = mSourceVoice->Stop( 0 );
				LN_THROW_Com( hr );

				mIsPausing = true;
			}
			// 一時停止を解除する場合で、現在一時停止している場合
			else if ( !isPause && mIsPausing )
			{
				hr = mSourceVoice->Start( 0 );
				LN_THROW_Com( hr );

				mIsPausing = false;
			}
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	bool OnMemory::polling()
	{
		// 再生中ではない場合は中断
		if ( !mSourceVoice || !mIsPlaying || mIsPausing )
		{
			return false;
		}

		// ループ再生ではない場合
		if ( !mIsLoop )
		{
			XAUDIO2_VOICE_STATE state;
			mSourceVoice->GetState( &state );

			// ボイスキューが空になった場合
			if ( state.BuffersQueued == 0 )
			{
				stop();
				return false;
			}
		}

        // 3D オーディオの更新
        if ( mEmitterState )
        {
            mManager->calcEmitterState( mEmitterState );

		    HRESULT hr = mSourceVoice->SetFrequencyRatio( mEmitterState->DSPSettings.DopplerFactor * ( mPitch * 0.01f ) );
			LN_THROW_Com( hr );

		    hr = mSourceVoice->SetOutputMatrix(
			    mManager->getMasteringVoice(),   // NULL でもいいみたいだけど一応
			    mEmitterState->DSPSettings.SrcChannelCount,
			    mEmitterState->DSPSettings.DstChannelCount,
			    mEmitterState->DSPSettings.pMatrixCoefficients );
			LN_THROW_Com( hr );
        }

        return true;
    }


//==============================================================================
// Streaming
//==============================================================================

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	Streaming::Streaming( AudioDevice* manager )
		: XAudio2AudioPlayerBase    ( manager )
        , mSourceVoice              ( NULL )
		, mPrimaryAudioData         ( NULL )
		, mSecondaryAudioData       ( NULL )
		, mAudioDataBufferSize      ( 0 )
		, mReadCursor               ( 0 )
        , mLoopBeginBlockOffset     ( 0 )
        , mCursorBeginPos           ( 0 )
        , mCursorEndPos             ( 0 )
        , mWriteSampleNum           ( 0 )
		, mEOF                      ( false )
        , mPassedLoopBeginSample    ( false )
	{
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	Streaming::~Streaming()
	{
        if ( mSourceVoice )
		{
			mSourceVoice->Stop();
			mSourceVoice->FlushSourceBuffers();
			mSourceVoice->DestroyVoice();
			mSourceVoice = NULL;
		}

		SAFE_DELETE_ARRAY( mPrimaryAudioData );
		SAFE_DELETE_ARRAY( mSecondaryAudioData );
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    void Streaming::initialize( AudioSourceBase* audio_source, bool enable_3d )
	{
		LN_ERR2_ASSERT( audio_source );

		XAudio2AudioPlayerBase::initialize( audio_source, enable_3d );

		// 1 秒分のバッファサイズ取得
		mAudioDataBufferSize = mAudioSource->getBytesPerSec();

		// 1 秒のバッファをふたつ用意
		mPrimaryAudioData	= LN_NEW lnU8[ mAudioDataBufferSize ];
		mSecondaryAudioData = LN_NEW lnU8[ mAudioDataBufferSize ];
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::setVolume( int volume )
	{
        XAudio2AudioPlayerBase::setVolume( volume );
        if ( mSourceVoice )
		{
			HRESULT hr = mSourceVoice->SetVolume( this->mVolume * 0.01f );
			LN_THROW_Com( hr );
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::setPitch( int pitch )
	{
        XAudio2AudioPlayerBase::setPitch( pitch );
        if ( mSourceVoice )
		{
			HRESULT hr = mSourceVoice->SetFrequencyRatio( this->mPitch * 0.01f );
			LN_THROW_Com( hr );
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
    lnU64 Streaming::getPlayedSamples() const
    {
        if ( mSourceVoice )
        {
            XAUDIO2_VOICE_STATE voice_state;
		    mSourceVoice->GetState( &voice_state );
		    return voice_state.SamplesPlayed;
        }
        return 0;
    }

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::play()
	{
        stop();

        // ソースボイス作成
        HRESULT hr;
        const WaveFormat* ln_format = mAudioSource->getWaveFormat();
		WAVEFORMATEX format;
        AudioUtil::convLNWaveFormatToWAVEFORMATEX( *ln_format, &format );

        hr = mManager->getXAudio2()->CreateSourceVoice( &mSourceVoice, &format );
		LN_THROW_Com( hr );

        hr = mSourceVoice->FlushSourceBuffers();
		LN_THROW_Com( hr );

		// デコード状態をリセットする ( MP3 用 )
		mAudioSource->reset();

		// ストリーミング読み込み位置リセット
		mReadCursor = 0;

        // とりあえずソースデータをカーソルの終端にする。
        // ファイル内から読むのは _addNextBuffer() で
        mCursorEndPos = mAudioSource->getSourceDataSize();
        
        mWriteSampleNum = 0;
        mCursorBeginPos = 0;
        mLoopBeginBlockOffset = 0;

		// 最初のデータをキューに追加する
		_addNextBuffer();

        hr = mSourceVoice->SetVolume( mVolume * 0.01f );
		LN_THROW_Com( hr );

        hr = mSourceVoice->SetFrequencyRatio( mPitch * 0.01f );
		LN_THROW_Com( hr );

		// 再生開始
		hr = mSourceVoice->Start();
		LN_THROW_Com( hr );

		mIsPlaying = true;
		mIsPausing = false;

		polling();	// 3D オーディオの計算を行うため
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::stop()
	{
		if ( mSourceVoice )
		{
			HRESULT hr;

			hr = mSourceVoice->Stop();
			LN_THROW_Com( hr );

			hr = mSourceVoice->FlushSourceBuffers();
			LN_THROW_Com( hr );

			mSourceVoice->DestroyVoice();
			mSourceVoice = NULL;
		}

		mIsPlaying = false;
		mIsPausing = false;
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::pause( bool isPause )
	{
		HRESULT hr;

		// 再生中の場合
		if ( mSourceVoice && mIsPlaying )
		{
			// 一時停止する場合で、現在一時停止していない場合
			if ( isPause && !mIsPausing )
			{
				hr = mSourceVoice->Stop( 0 );
				LN_THROW_Com( hr );

				mIsPausing = true;
			}
			// 一時停止を解除する場合で、現在一時停止している場合
			else if ( !isPause && mIsPausing )
			{
				hr = mSourceVoice->Start( 0 );
				LN_THROW_Com( hr );

				mIsPausing = false;
			}
		}
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	bool Streaming::polling()
	{
		// 再生中ではない場合は中断
		if ( !mSourceVoice || !mIsPlaying || mIsPausing )
		{
			return false;
		}

		// 状態の取得
		XAUDIO2_VOICE_STATE voice_state;
		mSourceVoice->GetState( &voice_state );

		// 再生が終了しておらず、キューにデータがふたつたまっていない場合
		if ( !mEOF && voice_state.BuffersQueued < 2 )
		{
			// 読み込みカーソルが終端を超えている場合
			if ( mReadCursor >= mCursorEndPos )
			{
				// ループ再生の場合、次にキューに追加するデータはループ領域の先頭であるため、読み込みカーソル位置を先頭に戻す
				if ( mIsLoop )
				{
					mReadCursor = mCursorBeginPos;
                    mCursorEndPos = mAudioSource->getSourceDataSize();
				}
				// ループ再生しない場合はファイルの最後まで読み切ったことを示すフラグを ON
				else
				{
					mEOF = true;
				}
			}

			// ファイルの最後まで読み切っていない場合
			if ( !mEOF )
			{
				// 次のデータを再生キューに追加
				_addNextBuffer();
			}
		}

		// 非ループ再生で、ファイルの最後まで読み切っている場合 ( 再生終了している場合 )
		if ( !mIsLoop && mEOF )
		{
			// キューが完全になくなった場合、停止とする
			if ( voice_state.BuffersQueued == 0 )
			{
				stop();
				return false;
			}
		}

        // 3D オーディオの更新
        if ( mEmitterState )
        {
            mManager->calcEmitterState( mEmitterState );

		    HRESULT hr = mSourceVoice->SetFrequencyRatio( mEmitterState->DSPSettings.DopplerFactor * ( mPitch * 0.01f ) );
			LN_THROW_Com( hr );

		    hr = mSourceVoice->SetOutputMatrix(
			    mManager->getMasteringVoice(),   // NULL でもいいみたいだけど一応
			    mEmitterState->DSPSettings.SrcChannelCount,
			    mEmitterState->DSPSettings.DstChannelCount,
			    mEmitterState->DSPSettings.pMatrixCoefficients );
			LN_THROW_Com( hr );
        }

		return true;
	}

	//----------------------------------------------------------------------
	//
	//----------------------------------------------------------------------
	void Streaming::_addNextBuffer()
	{
		Threading::ScopedLock lock( mLock );
        HRESULT hr;

#if 0
        // 読み込み位置に移動
		mAudioSource->seek( mReadCursor );

		// データ読み込み
		lnU32 read_size;		// 呼んだサイズ
		lnU32 write_size;		// デコードして書き込んだサイズ
		mAudioSource->read( mSecondaryAudioData, mAudioDataBufferSize, &read_size, &write_size );
#endif

		// データ読み込み
		lnU32 read_size;		// 呼んだサイズ
		lnU32 write_size;		// デコードして書き込んだサイズ
		mAudioSource->readSafe( mSecondaryAudioData, mAudioDataBufferSize, mReadCursor, &read_size, &write_size );

		// 実際に元データから読んだデータ分、カーソルを進める
		mReadCursor += read_size;

        // 書きこんだサイズを加算
        mWriteSampleNum += write_size;

        lnU8* data_buffer = mSecondaryAudioData;

        // ループ上で扱うサンプル数は、チャンネル数関係無しになってる。
        // それを正しい値に変換するため、実際の 1 サンプルあたりのバイト数を計算する
        lnU32 smp_size =
            mAudioSource->getWaveFormat()->Channels *
            mAudioSource->getWaveFormat()->BitsPerSample / 8;

        if ( mIsLoop )
        {
            if ( mLoopBegin == 0 && mLoopLength == 0 )
            {
                mCursorBeginPos = 0;
                mCursorEndPos = mAudioSource->getSourceDataSize();
            }
            else
            {
                // 読んだデータ内にループ領域の開始位置が含まれている場合
                if ( mPassedLoopBeginSample && mReadCursor - read_size == mCursorBeginPos )
                {
                    data_buffer = ( data_buffer + mLoopBeginBlockOffset );
                    write_size -= mLoopBeginBlockOffset; 
                }
                // ループ領域の開始位置が未設定で、ループ領域の開始サンプルを超えた場合
                else if ( mLoopBegin > 0 && !mPassedLoopBeginSample && mWriteSampleNum > ( mLoopBegin * smp_size ) )
                {
                    // カーソルはブロック単位。直前の read() のひとつ前を指す
                    mCursorBeginPos = mReadCursor - read_size;

                    // ブロック先頭からのサンプル数
                    mLoopBeginBlockOffset = write_size - ( mWriteSampleNum - ( mLoopBegin * smp_size ) );

                    mPassedLoopBeginSample = true;
                }

                //printf( "%d %d %d\n", mLoopBegin, mLoopLength, ( mLoopBegin + mLoopLength ) );
                //printf( "b:%d l:%d n:%d a:%d\n", mLoopBegin, mLoopLength, mWriteSampleNum, ( mLoopBegin + mLoopLength ) * smp_size );

                // 終端サンプル数を超えた場合、そこが終端
                if ( mLoopLength > 0 && mWriteSampleNum > ( mLoopBegin + mLoopLength ) * smp_size )
                {
                    // 終端。polling() でも検出する
                    mCursorEndPos = mReadCursor;

                    write_size -= ( mWriteSampleNum - ( mLoopBegin + mLoopLength ) * smp_size );

                    mWriteSampleNum = mCursorBeginPos;
                }
            }
        }
        
		// SourceVoice に送信するデータの設定
		XAUDIO2_BUFFER buffer = { 0 };

		// 現在位置がファイルの終端に達している場合
		if ( !mIsLoop && mReadCursor >= mCursorEndPos )//mAudioSource->getSourceDataSize() )
		{
			// フラグ追加 ( 最後まで演奏した時、GetState で取得する演奏時間をリセットする )
			buffer.Flags = XAUDIO2_END_OF_STREAM;
		}

		buffer.AudioBytes = write_size;
		buffer.pAudioData = data_buffer;

		//static int a = 0;
		//LRefString str;
		//str.format( "test%d.pcm", a);
		//FileIO::File::writeAllBytes( str.c_str(), buffer.pAudioData, buffer.AudioBytes );
		//++a;

		// SourceVoice にデータを送る
        hr = mSourceVoice->SubmitSourceBuffer( &buffer );
		LN_THROW_Com( hr );

		// primary と secondary の入れ替え
		lnU8* temp = mPrimaryAudioData;
		mPrimaryAudioData = mSecondaryAudioData;
		mSecondaryAudioData = temp;
	}

} // namespace XAudio2
} // namespace Audio
} // namespace Core
} // namespace LNote

//==============================================================================
//
//==============================================================================